#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <pthread.h>
#include <syslog.h>

#include <SDL.h>
#include <jpeglib.h>


#include "../../utils.h"
#include "../../capture.h"
#include "roateBitmap.h"

#define OUTPUT_PLUGIN_NAME "VIEWER output plugin"

#define MAX_ARGUMENTS 30

static pthread_t worker;
static GLOBALS *pGlobal;
static unsigned char *frame = NULL;
static unsigned char * dstData = NULL;

static int target_mode = 0;
/******************************************************************************
Description.: print a help message
Input Value.: -
Return Value: -
******************************************************************************/
static void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n" \
            " Help for output plugin..: "OUTPUT_PLUGIN_NAME"\n" \
            " [-t| --target ].........: will use 240x320 display mode. if the resolution is 320x240, i will roate it.\n" \
            " ---------------------------------------------------------------\n");
}

/******************************************************************************
Description.: clean up allocated ressources
Input Value.: unused argument
Return Value: -
******************************************************************************/
static void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if(!first_run) {
        //DBG("already cleaned up ressources\n");
        return;
    }

    first_run = 0;
    ////OPRINT("cleaning up ressources allocated by worker thread\n");

    //rgbimage.buffer 在工作线程第一次执行后，已经释放。
    free(frame);
    if(target_mode)
    {
      free(dstData);
    }
    SDL_Quit();
}

typedef struct {
    struct jpeg_source_mgr pub;

    Uint8 *jpegdata;
    int jpegsize;
} my_source_mgr;

static void init_source(j_decompress_ptr cinfo)
{
    return;
}

static int fill_input_buffer(j_decompress_ptr cinfo)
{
    my_source_mgr * src = (my_source_mgr *) cinfo->src;

    src->pub.next_input_byte = src->jpegdata;
    src->pub.bytes_in_buffer = src->jpegsize;

    return TRUE;
}

static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
    my_source_mgr * src = (my_source_mgr *) cinfo->src;

    if(num_bytes > 0) {
        src->pub.next_input_byte += (size_t) num_bytes;
        src->pub.bytes_in_buffer -= (size_t) num_bytes;
    }
}

static void term_source(j_decompress_ptr cinfo)
{
    return;
}

static void jpeg_init_src(j_decompress_ptr cinfo, Uint8 *jpegdata, int jpegsize)
{
    my_source_mgr *src;

    if(cinfo->src == NULL) {  /* first time for this JPEG object? */
        cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_source_mgr));
        src = (my_source_mgr *) cinfo->src;
    }

    src = (my_source_mgr *) cinfo->src;
    src->pub.init_source = init_source;
    src->pub.fill_input_buffer = fill_input_buffer;
    src->pub.skip_input_data = skip_input_data;
    src->pub.resync_to_restart = jpeg_resync_to_restart;
    src->pub.term_source = term_source;
    src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
    src->pub.next_input_byte = NULL; /* until buffer loaded */

    src->jpegdata = jpegdata;
    src->jpegsize = jpegsize;
    
}

static void my_error_exit(j_common_ptr cinfo)
{
    //DBG("JPEG data contains an error\n");
}

static void my_error_output_message(j_common_ptr cinfo)
{
    //DBG("JPEG data contains an error\n");
}

typedef struct {
    int height;
    int width;
    unsigned char *buffer;
    int buffersize;
} decompressed_image;

static int decompress_jpeg(unsigned char *jpeg, int jpegsize, decompressed_image *image)
{
    struct jpeg_decompress_struct cinfo;
    JSAMPROW rowptr[1];
    struct jpeg_error_mgr jerr;
    int Bpp;

    /* create an error handler that does not terminate MJPEG-streamer */
    cinfo.err = jpeg_std_error(&jerr);
    jerr.error_exit = my_error_exit;
    jerr.output_message = my_error_output_message;

    /* create the decompressor structures */
    jpeg_create_decompress(&cinfo);

    /* initalize the structures of decompressor */
    jpeg_init_src(&cinfo, jpeg, jpegsize);

    /* read the JPEG header data */
    if(jpeg_read_header(&cinfo, TRUE) < 0) {
        jpeg_destroy_decompress(&cinfo);
        //DBG("could not read the header\n");
        return 1;
    }

    /*
     * I just expect RGB colored JPEGs, so the num_components must be three
     */
    if(cinfo.num_components != 3) {
        jpeg_destroy_decompress(&cinfo);
        //DBG("unsupported number of components (~colorspace)\n");
        return 1;
    }

    /* just use RGB output and adjust decompression parameters */
    cinfo.out_color_space = JCS_RGB;
    cinfo.quantize_colors = FALSE;
    /* to scale the decompressed image, the fraction could be changed here */
    cinfo.scale_num   = 1;
    cinfo.scale_denom = 1;
    cinfo.dct_method = JDCT_FASTEST;
    cinfo.do_fancy_upsampling = FALSE;

    jpeg_calc_output_dimensions(&cinfo);

    /* store the image information */
    image->width = cinfo.output_width;
    image->height = cinfo.output_height;    
    
    if(target_mode)
    {
      if(image->width != 320 || image->height != 240)
      {
	fprintf(stderr, "can't use %dx%d resolution mode in target!", image->width, image->height);
	exit(EXIT_FAILURE);
      }
    }

    /*
     * just allocate a new buffer if not already allocated
     * pay a lot attention, that the calling function has to ensure, that the buffer
     * must be large enough
     */
    Bpp = cinfo.num_components;
    if(image->buffer == NULL) {
        image->buffersize = image->width * image->height * cinfo.num_components;
	fprintf(stderr, "bpp is %d\n", cinfo.num_components);	
        /* the calling function has to ensure that this buffer will become freed after use! */
        image->buffer = malloc(image->buffersize);
	
        if(image->buffer == NULL) {
            jpeg_destroy_decompress(&cinfo);
            perror("allocating memory failed\n");
            exit(EXIT_FAILURE);
        }
    }
    
    if(target_mode && dstData == NULL)
    {
      dstData = malloc(image->buffersize);
      if(NULL == dstData )
      {
	jpeg_destroy_decompress(&cinfo);
        perror("allocating memory failed\n");
        exit(EXIT_FAILURE);
      }
    }

    /* start to decompress */
    if(jpeg_start_decompress(&cinfo) < 0) {
        jpeg_destroy_decompress(&cinfo);
        //DBG("could not start decompression\n");
        return 1;
    }

    while(cinfo.output_scanline < cinfo.output_height) {
        rowptr[0] = (JSAMPROW)(Uint8 *)image->buffer + cinfo.output_scanline * image->width * cinfo.num_components;

        if(jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1) < 0) {
            jpeg_destroy_decompress(&cinfo);
            //DBG("could not decompress this line\n");
            return 1;
        }
    }

    if(jpeg_finish_decompress(&cinfo) < 0) {
        jpeg_destroy_decompress(&cinfo);
        //DBG("could not finish compression\n");
        return 1;
    }

    /* all is done */
    jpeg_destroy_decompress(&cinfo);
    /*FILE *fp;
    fp = fopen("/home/cf/tmp/test.bmp", "wb");
    fwrite(image->buffer, image->buffersize, 1, fp);
    fclose(fp);*/
    
    if(target_mode)
    {
	RoateData(image->width, image->height, image->buffer, 
		      dstData, Bpp);    
	/*
	unsigned char * tmpPoint;
	tmpPoint = image->buffer;
	image->buffer = dstData;
	dstData = tmpPoint;
	*/
	memcpy(image->buffer, dstData, image->buffersize);
	int tmpInt;
	tmpInt = image->width;
	image->width = image->height;
	image->height = tmpInt;
    }
    
    
     return 0;
}

/******************************************************************************
Description.: this is the main worker thread
              it loops forever, grabs a fresh frame, decompressed the JPEG
              and displays the decoded data using SDL
Input Value.:
Return Value:
******************************************************************************/
static void *worker_thread(void *arg)
{
    int frame_size = 0, firstrun = 1;

    SDL_Surface *screen = NULL, *image = NULL;
    decompressed_image rgbimage;

    /* initialze the buffer for the decompressed image */
    rgbimage.buffersize = 0;
    rgbimage.buffer = NULL;

    /* initialze the SDL video subsystem */
    if(SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    /* just allocate a large buffer for the JPEGs */
    if((frame = malloc(4096 * 1024)) == NULL) {
        ////OPRINT("not enough memory for worker thread\n");
        exit(EXIT_FAILURE);
    }

    /* set cleanup handler to cleanup allocated ressources */
    pthread_cleanup_push(worker_cleanup, NULL);

    while(!pGlobal->stopFlag) {
        fprintf(stderr, "\tthis is output_viewer, start fresh SDL frame .........\n");
        pthread_mutex_lock(&pGlobal->db);
        pthread_cond_wait(&pGlobal->db_update, &pGlobal->db);

        /* read buffer */
        frame_size = pGlobal->bufSize;
        memcpy(frame, pGlobal->buf, frame_size);

        pthread_mutex_unlock(&pGlobal->db);

        /* decompress the JPEG and store results in memory */
        if(decompress_jpeg(frame, frame_size, &rgbimage)) {
            //DBG("could not properly decompress JPEG data\n");
            continue;
        }

        if(firstrun) {
            /* create the primary surface (the visible window) */
            //screen = SDL_SetVideoMode(rgbimage.width, rgbimage.height, 0, SDL_ANYFORMAT | SDL_HWSURFACE);
	    if ( ( screen = SDL_SetVideoMode(rgbimage.width, rgbimage.height, 0, SDL_ANYFORMAT | SDL_HWSURFACE) ) == NULL ) {
		fprintf(stderr, "Couldn't set %dx%d video mode: %s\n",
			rgbimage.width, rgbimage.height,  SDL_GetError());
		exit(EXIT_FAILURE);
	    }
            SDL_WM_SetCaption("MJPG-Streamer Viewer", NULL);

            /* create a SDL surface to display the data */
            image = SDL_AllocSurface(SDL_SWSURFACE, rgbimage.width, rgbimage.height, 24,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
                                     0x0000FF, 0x00FF00, 0xFF0000,
#else
                                     0xFF0000, 0x00FF00, 0x0000FF,
#endif
                                     0);

            /* copy the decoded data across */
            memcpy(image->pixels, rgbimage.buffer, rgbimage.width * rgbimage.height * 3);
            free(rgbimage.buffer);

            /* now, that we know the dimensions, we can directly copy to the right surface */
            rgbimage.buffer = image->pixels;
            rgbimage.buffersize = rgbimage.width * rgbimage.height * 3;

            firstrun = 0;
        }

        /* copy the image to the primary surface */
        SDL_BlitSurface(image, NULL, screen, NULL);

        /* redraw the whole surface */
        SDL_Flip(screen);
	
        fprintf(stderr, "\tthis is output_viewer, complete fresh SDL frame .........\n\n");
    }

    pthread_cleanup_pop(1);

    /* get rid of the image */
    SDL_FreeSurface(image);

    return ( (void *) 0);
}


static void ParseCmdLine(char *outputParam)
{
     int i;
    char *argv[MAX_ARGUMENTS]={NULL, };
    int argc=1;
   
    char *str =strchr(outputParam, ' ');
    // convert the single parameter-string to an array of strings 
    argv[0] = OUTPUT_PLUGIN_NAME;
    if ( str != NULL && strlen(str) != 0 ) {
        char *arg=NULL, *saveptr=NULL, *token=NULL;

        arg=(char *)strdup(str);
    
        if ( strchr(arg, ' ') != NULL ) {
            token=strtok_r(arg, " ", &saveptr);
            if ( token != NULL ) {
                argv[argc] = strdup(token);
                argc++;
                while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {
                    argv[argc] = strdup(token);
                    argc++;
                    if (argc >= MAX_ARGUMENTS) {
                        fprintf(stderr, "ERROR: too many arguments to input plugin\n");
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    }

    // show all parameters for DBG purposes 
    for (i=0; i<argc; i++) {
        fprintf(stderr, "argv[%d]=%s\n", i, argv[i]);
    }
  
  
    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] =  {
            {"h", no_argument, 0, 0},
            {"help", no_argument, 0, 0},
	    {"t", no_argument, 0, 0},
            {"target", no_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(argc, argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            exit(EXIT_FAILURE);
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            //DBG("case 0,1\n");
            help();
            exit(EXIT_SUCCESS);
            break;        
	    
	        /* t. target  target_mode */
        case 2:
        case 3:
            target_mode = 1;
            break;
	default:
	    break;
        }
    }
}


/*** plugin interface functions ***/
/******************************************************************************
Description.: this function is called first, in order to initialise
              this plugin and pass a parameter string
Input Value.: parameters
Return Value: 0 if everything is ok, non-zero otherwise
******************************************************************************/
int output_init(char *outputParam, void *pglobal)
{
  pGlobal = (GLOBALS *) pglobal;
  if(pGlobal->avi_mode)
  {
    fprintf(stderr, "can't use output viewer mode with use output avi mode!!\n");
    exit(EXIT_FAILURE);
  }
   ParseCmdLine(outputParam);
    return 0;
}

/******************************************************************************
Description.: calling this function stops the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_stop(int id)
{
    int terr;
    void * tret;
    terr = pthread_join(worker, &tret);
    if(terr != 0)
    {
      perror("join thread output_viewer worker_thread error!");
      exit(EXIT_FAILURE);
    }
    //资源的释放在 worker_cleanup 中完成。
    //pthread_cancel(worker);
    return 0;
}

/******************************************************************************
Description.: calling this function creates and starts the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_run(int id)
{
    //DBG("launching worker thread\n");
    pthread_create(&worker, 0, worker_thread, NULL);
    //使用了 pthread_join， 固不再使用线程分离
    //pthread_detach(worker);
    return 0;
}

int output_cmd()
{
    //DBG("Commands supported for the %s", OUTPUT_PLUGIN_NAME);
    return 0;
}

